package com.zys.sac.core.config;

import com.zys.sac.core.constant.RequestStatus;
import com.zys.sac.core.constant.RoutePolicy;
import com.zys.sac.core.entity.SacPermission;
import com.zys.sac.core.entity.SacRole;
import com.zys.sac.core.exception.SacAuthorizationException;
import com.zys.sac.core.filter.SacCheckFilter;
import com.zys.sac.core.helper.UrlRule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 将这个类抽象成抽象类，其中涉及到数据库的地方由实现类自行实现
 *
 * Created by zhongjunkai on 2022/3/11.
 */
public abstract class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource, CommandLineRunner, Ordered {

    @Autowired
    private SacCheckFilter sacCheckFilter;

    private String urlRegx = "perm\\[(.+(\\,.+)*)\\]";

    private Pattern urlRulePattern = Pattern.compile(urlRegx);

    private Map<UrlRule, List<ConfigAttribute>> permUrlRuleConfig = new HashMap<>();

    public static final Integer ORDER = 11;

    @Override
    public int getOrder() {
        return ORDER;
    }

    @Override
    public void run(String... args) throws Exception {
        initPermUrlRuleConfig();
    }

    private void initPermUrlRuleConfig() {
        List<UrlRule> urlRules = sacCheckFilter.urlRules();
        if (null != urlRules && urlRules.size() > 0) {
            for (UrlRule urlRule : urlRules) {
                String rule = urlRule.getRule();
                Matcher matcher = urlRulePattern.matcher(rule);
                List<ConfigAttribute> securityConfigs = new ArrayList<>();
                while (matcher.find()) {
                    String[] roles = matcher.group(1).split(",");
                    for (String role : roles) {
                        securityConfigs.add(new SecurityConfig("ROLE_" + role.trim()));
                    }
                }
                permUrlRuleConfig.put(urlRule, securityConfigs);
            }
        }
    }

    /**
     * 该方法的主要作用是返回访问url对应的权限（可以是角色，也可以是authorizes资源）
     *
     * @param object
     * @return
     * @throws IllegalArgumentException
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        List<AntPathRequestMatcher> avoidMatchers = sacCheckFilter.avoidMatchers();
        boolean avoid = false;
        HttpServletRequest request = ((FilterInvocation) object).getRequest();
        if(!CollectionUtils.isEmpty(avoidMatchers)) {
            for(AntPathRequestMatcher matcher : avoidMatchers) {
                avoid = matcher.matches(request);
                if(avoid) {
                    break;
                }
            }
        }
        // *注意：如果configAttributes中未包含任何资源（集合是空的），那么UrlAccessDecisionManager中的decide方法是不会执行的
        List<ConfigAttribute> configAttributes = new ArrayList<>();
        if(!avoid) {
            // 只验证登录的路径处理
            List<AntPathRequestMatcher> onlyLoginMatchers = sacCheckFilter.onlyLoginPathMatchers();
            if (!CollectionUtils.isEmpty(onlyLoginMatchers)) {
                boolean onlyLogin = false;
                for (AntPathRequestMatcher matcher : onlyLoginMatchers) {
                    onlyLogin = matcher.matches(request);
                    if (onlyLogin) {
                        break;
                    }
                }
                if (onlyLogin) {
                    return configAttributes;
                }
            }
            // /login?logout
            String requestUrl = ((FilterInvocation) object).getRequestUrl();
            if(requestUrl.indexOf("?") > 0) {
                requestUrl = requestUrl.substring(0, requestUrl.indexOf("?"));
            }
            //这个url是spring security特殊返回的，在执行登出的时候会使用到这个，这里就直接返回空集合，防止后续的url验证操作
            if ("/login?logout".equals(requestUrl)) {
                return configAttributes;
            }
            // 检查路由规则
            if (!permUrlRuleConfig.isEmpty()) {
                Set<UrlRule> urlRules = permUrlRuleConfig.keySet();
                for (UrlRule urlRule : urlRules) {
                    if (urlRule.match(request)) {
                        return permUrlRuleConfig.get(urlRule);
                    }
                }
            }
            //找到当前url对应的权限实体
            SacPermission targetPermission = findPermissionByUrl(requestUrl);
            if (targetPermission == null) {
                RoutePolicy routePolicy = routePolicy();
                // 当策略为PASS_ALL时，不做权限验证
                if (RoutePolicy.PASS_ALL == routePolicy) {
                    return configAttributes;
                } else { // 对未配置的权限提示权限异常
                    throw new SacAuthorizationException(RequestStatus.INVALIDURL.getCode(), RequestStatus.INVALIDURL.getMsg());
                }
            }
            //找到目标权限对应的所有角色信息
            List<? extends SacRole> permissionRoles = findPermissionRole(targetPermission.getCode());
            if (CollectionUtils.isEmpty(permissionRoles)) {
                throw new SacAuthorizationException(RequestStatus.NOURL2ROLE.getCode(), RequestStatus.NOURL2ROLE.getMsg());
            }
            if (!CollectionUtils.isEmpty(permissionRoles)) {
                permissionRoles.stream().forEach(role ->
                        configAttributes.add(new SecurityConfig("ROLE_" + role.getCode()))
                );
            }
        }
        return configAttributes;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }

    /**
     * 路由策略，{@link RoutePolicy#PASS_ALL}和{@link RoutePolicy#DENY_ALL}，
     * PASS_ALL：表示所有资源都不进行角色权限控制
     * DENY_ALL: 表示所有资源都需要配置角色的权限
     *
     * @return
     */
    public RoutePolicy routePolicy() {
        return RoutePolicy.PASS_ALL;
    }

    /**
     * 根据url查找权限
     *
     * @param url 请求路径，contextPath之后的uri部分
     * @return
     */
    protected abstract SacPermission findPermissionByUrl(String url);

    /**
     * 根据权限编码查询对应的角色
     *
     * @param permissionCode
     * @return
     */
    protected abstract List<? extends SacRole> findPermissionRole(String permissionCode);

}
